#include "stdafx.h"
#include "pb/Application.h"
#include "pb/PNGToBMP32.h"

pb::Application::Application() :
    m_hwnd(nullptr),
    m_pD2DFactory(nullptr),
    m_pDWriteFactory(nullptr),
    m_pRenderTarget(nullptr),
    m_pTextFormatLarge(nullptr),
    m_pTextLayoutLarge(nullptr),
    m_pTextFormatSmall(nullptr),
    m_pWhiteBrush(nullptr)
{
}

pb::Application::~Application()
{
}

HRESULT pb::Application::Initialize()
{
    HRESULT hr;

    hr = CreateDeviceIndependentResources();
    if (SUCCEEDED(hr)) {
        WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
        wcex.style         = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc   = Application::WndProc;
        wcex.cbClsExtra    = 0;
        wcex.cbWndExtra    = sizeof(LONG_PTR);
        wcex.hInstance     = HINST_THISCOMPONENT;
        wcex.hbrBackground = NULL;
        wcex.lpszMenuName  = NULL;
        wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wcex.lpszClassName = L"Application";

        RegisterClassEx(&wcex);

        FLOAT dpiX, dpiY;
        m_pD2DFactory->GetDesktopDpi(&dpiX, &dpiY);

        m_hwnd = CreateWindow(
                     L"Application",
                     L"PNG to 32bit BMP",
                     WS_OVERLAPPEDWINDOW,
                     CW_USEDEFAULT,
                     CW_USEDEFAULT,
                     static_cast<UINT>(ceil(640.f * dpiX / 96.f)),
                     static_cast<UINT>(ceil(240.f * dpiY / 96.f)),
                     NULL,
                     NULL,
                     HINST_THISCOMPONENT,
                     this
                 );
        hr = m_hwnd ? S_OK : E_FAIL;
        if (SUCCEEDED(hr)) {
            DragAcceptFiles(m_hwnd, TRUE);

            ShowWindow(m_hwnd, SW_SHOWNORMAL);

            UpdateWindow(m_hwnd);
        }
    }

    return hr;
}

void pb::Application::RunMessageLoop()
{
    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

HRESULT pb::Application::CreateDeviceIndependentResources()
{
    static const WCHAR msc_fontName[] = L"Times New Roman";
    static const FLOAT msc_fontSize = 40;
    HRESULT hr;
    ID2D1GeometrySink *pSink = NULL;

    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);

    if (SUCCEEDED(hr)) {
        hr = DWriteCreateFactory(
                 DWRITE_FACTORY_TYPE_SHARED,
                 __uuidof(m_pDWriteFactory),
                 reinterpret_cast<IUnknown **>(&m_pDWriteFactory)
             );
    }
    if (SUCCEEDED(hr)) {
        hr = m_pDWriteFactory->CreateTextFormat(
                 msc_fontName,
                 NULL,
                 DWRITE_FONT_WEIGHT_NORMAL,
                 DWRITE_FONT_STYLE_ITALIC,
                 DWRITE_FONT_STRETCH_NORMAL,
                 msc_fontSize,
                 L"", //locale
                 &m_pTextFormatLarge
             );
    }
    if (SUCCEEDED(hr)) {
        m_pTextFormatLarge->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);

        m_pTextFormatLarge->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);

    }
    if (SUCCEEDED(hr)) {
        hr = m_pDWriteFactory->CreateTextFormat(
                 msc_fontName,
                 NULL,
                 DWRITE_FONT_WEIGHT_NORMAL,
                 DWRITE_FONT_STYLE_ITALIC,
                 DWRITE_FONT_STRETCH_NORMAL,
                 msc_fontSize / 2,
                 L"", //locale
                 &m_pTextFormatSmall
             );
    }
    if (SUCCEEDED(hr)) {
        m_pTextFormatSmall->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);

        m_pTextFormatSmall->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);

    }

    SafeRelease(&pSink);

    return hr;
}

HRESULT pb::Application::CreateDeviceResources()
{
    HRESULT hr = S_OK;

    if (!m_pRenderTarget) {

        RECT rc;
        GetClientRect(m_hwnd, &rc);

        D2D1_SIZE_U size = D2D1::SizeU(
                               rc.right - rc.left,
                               rc.bottom - rc.top
                           );

        hr = m_pD2DFactory->CreateHwndRenderTarget(
                 D2D1::RenderTargetProperties(),
                 D2D1::HwndRenderTargetProperties(m_hwnd, size),
                 &m_pRenderTarget
             );
        if (SUCCEEDED(hr)) {
            hr = m_pRenderTarget->CreateSolidColorBrush(
                     D2D1::ColorF(D2D1::ColorF::White),
                     &m_pWhiteBrush
                 );
        }

    }

    return hr;
}

void pb::Application::DiscardDeviceResources()
{

}

HRESULT pb::Application::OnRender()
{
    HRESULT hr;

    hr = CreateDeviceResources();

    if (SUCCEEDED(hr) && !(m_pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED)) {
        static const WCHAR sc_mainText[] = L"Drag and drop your PNG files here.";
        static const WCHAR sc_subText[] = L"Caution: BMP files will always be overwritten.";

        D2D1_SIZE_F renderTargetSize = m_pRenderTarget->GetSize();

        m_pRenderTarget->BeginDraw();

        m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());

        m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black));

        m_pTextLayoutLarge = nullptr;
        m_pDWriteFactory->CreateTextLayout(
            sc_mainText,
            ARRAYSIZE(sc_mainText) - 1,
            m_pTextFormatLarge,
            renderTargetSize.width,
            renderTargetSize.height,
            &m_pTextLayoutLarge);

        DWRITE_TEXT_METRICS metrics;
        m_pTextLayoutLarge->GetMetrics(&metrics);

        m_pRenderTarget->DrawTextLayout(D2D1::Point2F(0.0f, -20.0f), m_pTextLayoutLarge, m_pWhiteBrush);

        m_pRenderTarget->DrawText(
            sc_subText,
            ARRAYSIZE(sc_subText) - 1,
            m_pTextFormatSmall,
            D2D1::RectF(0, metrics.height + 25.0f, renderTargetSize.width, renderTargetSize.height),
            m_pWhiteBrush
        );

        hr = m_pRenderTarget->EndDraw();

        if (hr == D2DERR_RECREATE_TARGET) {
            hr = S_OK;
            DiscardDeviceResources();
        }
    }

    return hr;
}

void pb::Application::OnResize( UINT width, UINT height )
{
    if (m_pRenderTarget) {
        D2D1_SIZE_U size;
        size.width = width;
        size.height = height;

        m_pRenderTarget->Resize(size);
    }
}

LRESULT CALLBACK pb::Application::WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    LRESULT result = 0;

    if (message == WM_CREATE) {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        Application *pApp = (Application *)pcs->lpCreateParams;

        ::SetWindowLongPtrW(
            hWnd,
            GWLP_USERDATA,
            PtrToUlong(pApp)
        );

        result = 1;
    } else {
        Application *pApp = reinterpret_cast<Application *>(static_cast<LONG_PTR>(
                                ::GetWindowLongPtrW(
                                    hWnd,
                                    GWLP_USERDATA
                                )));

        bool wasHandled = false;

        if (pApp) {
            switch (message) {
            case WM_SIZE: {
                UINT width = LOWORD(lParam);
                UINT height = HIWORD(lParam);
                pApp->OnResize(width, height);
            }
            wasHandled = true;
            result = 0;
            break;

            case WM_PAINT:
            case WM_DISPLAYCHANGE: {
                PAINTSTRUCT ps;
                BeginPaint(hWnd, &ps);

                pApp->OnRender();
                EndPaint(hWnd, &ps);
            }
            wasHandled = true;
            result = 0;
            break;

            case WM_DROPFILES: {
                pApp->OnDropFiles((HDROP)wParam);
            }
            break;

            case WM_DESTROY: {
                PostQuitMessage(0);
            }
            wasHandled = true;
            result = 1;
            break;
            }
        }

        if (!wasHandled) {
            result = DefWindowProc(hWnd, message, wParam, lParam);
        }
    }

    return result;
}

void pb::Application::OnDropFiles( HDROP hDrop )
{
    UINT numberOfFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
    ATLTRACE("number of files = %d\n", numberOfFiles);

    std::vector<std::wstring> fileNames(numberOfFiles);
    for (UINT i = 0; i < numberOfFiles; ++i) {
        UINT len = DragQueryFile(hDrop, i, nullptr, 0);
        std::vector<wchar_t> fileName(len + 1);
        UINT ret = DragQueryFile(hDrop, i, &fileName.front(), len + 1);
        if (ret) {
            fileNames[i] = std::wstring(&fileName.front());
        }
    }

    std::wregex rx(L".*\\.[Pp][Nn][Gg]");
    for (UINT i = 0; i < numberOfFiles; ++i) {
        ATLTRACE(L"%s\n", fileNames[i].c_str());
        std::wstring &pngFileName = fileNames[i];

        std::wstring bmpFileName(pngFileName);
        if (std::regex_match(std::begin(pngFileName), std::end(pngFileName), rx)) {
            bmpFileName = bmpFileName.substr(0, bmpFileName.length() - 4) + L".bmp";
            pb::PNGToBMP32 pb;
            pb.Convert(pngFileName, bmpFileName);
        }
    }
}
